package edu.zzuli.common.security;

import org.acegisecurity.AccountExpiredException;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.AuthenticationServiceException;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.CredentialsExpiredException;
import org.acegisecurity.DisabledException;
import org.acegisecurity.LockedException;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.providers.dao.DaoAuthenticationProvider;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.springframework.dao.DataAccessException;
import org.springframework.util.Assert;

public class CustomDaoAuthenticationProvider extends DaoAuthenticationProvider {
	
	protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		if (!(authentication instanceof CustomUsernamePasswordAuthenticationToken)&&!(authentication instanceof CustomUsernameAuthenticationToken)) {
			Object salt = null;
			if (this.getSaltSource() != null) {
				salt = this.getSaltSource().getSalt(userDetails);
			}
			if (!getPasswordEncoder().isPasswordValid(userDetails.getPassword(), authentication.getCredentials().toString(), salt)) { 
				throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);
			}
		}
	}

	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Assert.isInstanceOf( UsernamePasswordAuthenticationToken.class, authentication, messages .getMessage(
							"AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
		// Determine username
		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
		boolean cacheWasUsed = true;
		UserDetails user = this.getUserCache().getUserFromCache(username);

		if (user == null) {
			cacheWasUsed = false;
			try {
				user = retrieveUserCustom(username, (UsernamePasswordAuthenticationToken) authentication);
			} catch (UsernameNotFoundException notFound) {
				if (hideUserNotFoundExceptions) {
					throw new BadCredentialsException( messages .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
				} else {
					throw notFound;
				}
			}
			Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
		}

		if (!user.isAccountNonLocked()) {
			throw new LockedException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
		}

		if (!user.isEnabled()) {
			if (authentication instanceof CustomUsernamePasswordAuthenticationToken) {
				throw new CustomAuthenticationException("该证书绑定帐号还未启用或者已禁用！", CustomAuthenticationException.CODE_CA, -1);
			} else {
				throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
			}
		}

		if (!user.isAccountNonExpired()) {
			throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
		}

		// This check must come here, as we don't want to tell users
		// about account status unless they presented the correct credentials
		try {
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		} catch (AuthenticationException exception) {
			// There was a problem, so try again after checking we're using
			// latest data
			cacheWasUsed = false;
			user = retrieveUserCustom(username, (UsernamePasswordAuthenticationToken) authentication);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}

		if (!user.isCredentialsNonExpired()) {
			throw new CredentialsExpiredException(messages .getMessage( "AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
		}

		if (!cacheWasUsed) {
			this.getUserCache().putUserInCache(user);
		}

		Object principalToReturn = user;

		if (isForcePrincipalAsString()) {
			principalToReturn = user.getUsername();
		}

		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

	protected final UserDetails retrieveUserCustom(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		UserDetails loadedUser;
		if (authentication instanceof CustomUsernamePasswordAuthenticationToken) {
			try {
				loadedUser = ((CustomJdbcDaoImpl) this.getUserDetailsService())
						.loadUserByCaid(authentication.getName());
			} catch (DataAccessException repositoryProblem) {
				throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);
			}

			if (loadedUser == null) {
				throw new AuthenticationServiceException("AuthenticationDao returned null, which is an interface contract violation");
			}
		} else {
			loadedUser = this.retrieveUser(username, authentication);
		}
		return loadedUser;
	}
}
